Skip to content

fix(claude): bump adapter + claude-code to unblock Opus 4.7 in upstream image#474

Merged
thepagent merged 1 commit intoopenabdev:mainfrom
brettchien:fix/claude-opus-4.7
Apr 19, 2026
Merged

fix(claude): bump adapter + claude-code to unblock Opus 4.7 in upstream image#474
thepagent merged 1 commit intoopenabdev:mainfrom
brettchien:fix/claude-opus-4.7

Conversation

@brettchien
Copy link
Copy Markdown
Contributor

@brettchien brettchien commented Apr 19, 2026

What problem does this solve?

After #447 (0.7.8-beta.5) set ENV CLAUDE_CODE_EXECUTABLE=/usr/local/bin/claude, the adapter now runs the pinned @anthropic-ai/claude-code binary instead of its own bundled SDK cli.js. That fix was correct, but the versions pinned in the Dockerfile never learned about Opus 4.7:

  • claude-agent-acp@0.25.0 — hardcoded availableModels list tops out at Sonnet 4.6 / Haiku 4.5
  • claude-code@2.1.104 — model resolver knows up to claude-opus-4-6

Net result with the current image (ghcr.io/openabdev/openab-claude:0.7.8-beta.7):

ANTHROPIC_MODEL=opus
   ↓
adapter's availableModels ─── no "opus 4.7" entry ──→ user falls back to "default" = Sonnet 4.6

Users who want Opus 4.7 today either have to build the image locally with a newer adapter + CLI, or set CLAUDE_CODE_EXECUTABLE="" to get the adapter to use its own bundled cli.js — defeating #447's pinning semantics.

Closes #
Refs #326, #412, #418, #447
Discord Discussion URL: https://discord.com/channels/1491295327620169908/1491564498492850217/1495445157640929444

At a Glance

                 ┌──────────────────────────┐
Discord thread → │ openab (ACP broker)     │
                 └──────────┬───────────────┘
                            │ stdio JSON-RPC
                            ▼
           ┌────────────────────────────────────┐
           │ claude-agent-acp adapter (npm)    │
           │   availableModels list (hardcoded) │
           │   @0.25.0 → [default, sonnet, haiku]  ❌ no 4.7
           │   @0.29.2 → [default, sonnet, haiku, opus, opus[1m]]  ✅
           └────────────────┬───────────────────┘
                            │ spawn()
                            ▼
           ┌────────────────────────────────────┐
           │ ${CLAUDE_CODE_EXECUTABLE}         │
           │   = /usr/local/bin/claude        │
           │   model resolver's opus alias    │
           │   @2.1.104 → claude-opus-4-6     ❌
           │   @2.1.112+ → claude-opus-4-7    ✅
           └────────────────────────────────────┘

Both layers need to understand 4.7 for the end-to-end flow to work. Bumping only the adapter leaves the runtime CLI unable to route the model; bumping only the CLI leaves the availableModels list missing the option.

Prior Art & Industry Research

Proposed Solution

 ARG CLAUDE_CODE_VERSION=2.1.104
-RUN npm install -g @agentclientprotocol/claude-agent-acp@0.25.0 \
+ARG CLAUDE_AGENT_ACP_VERSION=0.29.2
+RUN npm install -g @agentclientprotocol/claude-agent-acp@${CLAUDE_AGENT_ACP_VERSION} \
                    @anthropic-ai/claude-code@${CLAUDE_CODE_VERSION} --retry 3

Paired bumps:

  • @agentclientprotocol/claude-agent-acp 0.25.0 → 0.29.2 — spans four minor releases that include:
    • 0.26.0 bundled sdk 0.2.96, TUI login for remote envs
    • 0.27.0 bundled sdk 0.2.104, raw-SDK message opt-in
    • 0.28.0 bundled sdk 0.2.109
    • 0.29.0 bundled sdk 0.2.111 → adds Opus 4.7 (release note)
    • 0.29.1, 0.29.2 — minor follow-ups on 0.29.0
  • @anthropic-ai/claude-code 2.1.104 → 2.1.114 — brings the model resolver table forward so opus alias maps to claude-opus-4-7.

Why this approach

  1. Preserves fix: wire CLAUDE_CODE_EXECUTABLE so adapter uses pinned claude-code #447's intent. The pinned CLI stays load-bearing; we bump its version so the pin actually delivers Opus 4.7. Removing ENV CLAUDE_CODE_EXECUTABLE would give the adapter its bundled cli.js (which knows 4.7) but undo the observability/reproducibility win of fix: wire CLAUDE_CODE_EXECUTABLE so adapter uses pinned claude-code #447.
  2. Consistent with fix: pin CLI versions in all Dockerfiles using ARG for reproducible builds #326 / fix: bump CLI versions — codex 0.121.0, gemini 0.38.1, copilot 1.0.30, cursor 2026.04.15 #412 ARG pattern. Adapter version becomes a first-class build knob, pinnable and overridable with --build-arg.
  3. Smallest diff that fully unblocks Opus 4.7. Three lines in one file. No source changes, no chart changes, no tests to rewrite.
  4. Reversible. docker build --build-arg CLAUDE_AGENT_ACP_VERSION=0.25.0 --build-arg CLAUDE_CODE_VERSION=2.1.104 ... pins back to today's exact image.
  5. Safer than when the bump window first opened. claude-code@2.1.113 dropped the bundled JavaScript CLI in favor of a per-platform pre-compiled binary (bin/claude.exe) and moved per-session state from /tmp/claude-<uid>/…/tasks/ to $HOME/.claude/{projects,tasks}/. The vulnerable filesystem layout behind anthropics/claude-code#49512 no longer exists in 2.1.114 (grep evidence in the risk section below). That removes the main reason fix: bump CLI versions — codex 0.121.0, gemini 0.38.1, copilot 1.0.30, cursor 2026.04.15 #412 held back; we are landing on 2.1.114 after the architectural shift, not on 2.1.112 where the race was first reported.

Alternatives Considered

Alternative Rejected because
Bump adapter only, leave CLAUDE_CODE_VERSION=2.1.104 Adapter's availableModels would advertise Opus 4.7 but the CLI's opus alias still maps to 4-6 at runtime; ACP session would report currentModelId=opus but the actual API call would use the older ID. Confusing partial fix.
Bump adapter only + remove ENV CLAUDE_CODE_EXECUTABLE Regresses #447 — pinned claude-code becomes dead code again (adapter uses its bundled cli.js). #418's analysis would come back.
Stay on claude-code@2.1.111 instead of 2.1.114 2.1.111 has #49503settings.json model preference silently ignored. Worse than #49512 for our case because it affects ANTHROPIC_MODEL routing (the very thing we're trying to wire up).
Pin at claude-code@2.1.110 Last version before the known regressions — but also before the Opus 4.7 model resolver entry. Doesn't solve the problem.

Known risk assessment: anthropics/claude-code#49512 — empirically not reproducible in 2.1.114

The referenced upstream issue was filed 2026-04-16 against claude-code@2.1.112 and reports an ENOENT race on mkdir '/tmp/claude-<uid>/-home-//tasks' when parallel sessions run as the same user on Linux. The issue is still OPEN but has had zero comments since filing and no upstream fix commit.

Why we believe it's already fixed incidentally: claude-code@2.1.113 changed the CLI to "spawn a native Claude Code binary (via a per-platform optional dependency) instead of bundled JavaScript" (release notes). This rewrote the session-state layout. In 2.1.114 the /tmp/claude-<uid>/... tree no longer exists — session state lives entirely under $HOME/.claude/. The exact path that triggered #49512's ENOENT is gone.

Code-level evidence from the 2.1.114 install:

$ ls /usr/local/lib/node_modules/@anthropic-ai/claude-code/
bin/  cli-wrapper.cjs  install.cjs  LICENSE.md  node_modules  package.json  ...

$ cat /usr/local/lib/node_modules/@anthropic-ai/claude-code/package.json | head
{
  "name": "@anthropic-ai/claude-code",
  "version": "2.1.114",
  "bin": { "claude": "bin/claude.exe" },
  ...
}

$ head -10 /usr/local/lib/node_modules/@anthropic-ai/claude-code/cli-wrapper.cjs
#!/usr/bin/env node
// Fallback launcher for the claude wrapper package (name in ./package.json).
//
// Normally the postinstall script copies the native binary over bin/claude.exe,
// so this file is never invoked. ...

i.e. there is no more bundled cli.js; /usr/local/bin/claude resolves to a pre-compiled bin/claude.exe (236 MB native binary). Binary-grep on that executable:

$ grep -aoE "/tmp/claude-[a-z0-9]+" bin/claude.exe | sort -u
/tmp/claude-hook
/tmp/claude-mcp
# no /tmp/claude-<uid>, no /tmp/claude-999 layout

$ grep -c -a "/home-//tasks" bin/claude.exe
0
# the exact path pattern from issue #49512 is not in the binary at all

$ grep -aoE "\.claude/(projects|sessions|tasks|backups|shell-snapshots)" bin/claude.exe | sort -u
.claude/projects
.claude/tasks
# per-session tasks dir is now under $HOME/.claude/, not /tmp/claude-<uid>/

Since each OS user gets a single $HOME/.claude/tasks/ namespace (rather than shared /tmp/claude-<uid>/ with sibling cleanup races), concurrent sessions as the same user no longer step on each other's directory lifecycles. The race's necessary precondition — overlapping mkdir/rmdir in a shared /tmp location — has been structurally removed.

Empirical verification (run 2026-04-19 against the proposed image):

# Test 1: 20 parallel `claude --help`
  → 20/20 exit 0, zero stderr, zero ENOENT traces

# Test 2: 10 parallel ACP `initialize` handshakes
  → 10/10 returned valid protocolVersion=1 responses from adapter 0.29.2
  → zero stderr, zero race traces

# Test 3: 20 parallel `claude -p "reply ok" --output-format json` (real API calls,
#         staggered by 50ms to force lifetime overlap)
  → 20/20 exit 0, zero stderr, all processes returned ~1150-byte JSON replies
  → `/tmp/claude-*` never created during the run (confirming path layout changed)
  → session files wrote to ~/.claude/projects/-home-node/<uuid>.jsonl instead

# Test 4 (aggressive): 50 parallel `claude -p` with mixed short/long prompts,
#                      40ms stagger (~2s spawn window), same user, same $HOME
  → 50/50 exit 0
  → 50/50 stdout contained a valid "result" payload (successful API responses)
  → 0 bytes total stderr across all 50 processes
  → zero ENOENT / mkdir / panic traces
  → still no `/tmp/claude-*` directories

The previously-vulnerable /tmp/claude-<uid>/-home-//tasks/ path is not used by 2.1.114. We consider #49512 effectively fixed by the 2.1.113 architecture change, even though the upstream issue is not yet formally closed.

Fallback plan if the race does surface in production: users can pin back with --build-arg CLAUDE_CODE_VERSION=2.1.110 (loses Opus 4.7 routing, returns to 4.6) while we file a new upstream issue with the new repro.

Validation

  • Build: docker build -f Dockerfile.claude -t openab-claude:test --build-arg CLAUDE_AGENT_ACP_VERSION=0.29.2 --build-arg CLAUDE_CODE_VERSION=2.1.114 . — clean

  • Pinned versions baked in: docker run --rm --entrypoint sh openab-claude:test -c 'claude --version && npm ls -g @agentclientprotocol/claude-agent-acp':

    2.1.114 (Claude Code)
    `-- @agentclientprotocol/claude-agent-acp@0.29.2
    
  • ACP exposes Opus 4.7: adapter 0.29.2's availableModels now contains:

    {"modelId":"opus","name":"Opus","description":"Opus 4.7 · Most capable for complex work"}

    and with ANTHROPIC_MODEL=opus, currentModelId=opus.

  • CLI actually routes to Opus 4.7 — verified in two forms:

    Full model ID (--model claude-opus-4-7):

    {"type":"result","subtype":"success","is_error":false,"result":"ok",
     "total_cost_usd":0.05067125,
     "modelUsage":{"claude-opus-4-7":{"inputTokens":6,"outputTokens":6,
       "contextWindow":200000,"maxOutputTokens":64000,"costUSD":0.05067125}}}

    Alias (--model opus) resolves to the same model:

    {"type":"result","subtype":"success","result":"ok",
     "modelUsage":{"claude-opus-4-7":{"inputTokens":6,"outputTokens":6,
       "contextWindow":200000,"maxOutputTokens":64000,"costUSD":0.05054625}}}

    Cost (~$0.05/call) is consistent with Opus 4.7 pricing. Pre-bump, the same alias resolved to claude-opus-4-6 (see issue referenced above).

  • Backward-compat override: --build-arg CLAUDE_AGENT_ACP_VERSION=0.25.0 --build-arg CLAUDE_CODE_VERSION=2.1.104 still builds and reproduces today's pre-bump behaviour.

  • #49512 stress test — 20 parallel claude -p sessions, 50ms stagger: 20/20 exit 0, zero stderr, no ENOENT, no /tmp/claude-* directories created.

  • #49512 aggressive stress — 50 parallel sessions, mixed short/long prompts, 40ms stagger (~2s spawn window): 50/50 exit 0, 50/50 valid API response, 0 bytes total stderr, zero ENOENT / mkdir / panic traces (see "Known risk assessment" above for the full command + output).

  • cargo check --all-targets clean (no Rust changes, sanity run from main).

  • cargo test passes (no Rust changes, sanity run from main).

  • helm unittest charts/openab passes (no chart changes).

Before this change, `openab-claude:0.7.8-beta.7` ships:
  - claude-agent-acp@0.25.0 — hardcoded model list, no Opus 4.7
  - claude-code@2.1.104     — knows up to Opus 4.6 only
  - ENV CLAUDE_CODE_EXECUTABLE=/usr/local/bin/claude (openabdev#447)

With openabdev#447 making the pinned claude-code binary load-bearing, neither
the adapter's availableModels nor the CLI's model resolver knows about
Opus 4.7 — users get Sonnet 4.6 regardless of ANTHROPIC_MODEL=opus.

This PR:
  - introduces `CLAUDE_AGENT_ACP_VERSION` ARG (pattern parity with
    `CLAUDE_CODE_VERSION` from openabdev#326/openabdev#412)
  - bumps adapter 0.25.0 → 0.29.2 (brings claude-agent-sdk 0.2.111+
    whose availableModels includes Opus 4.7)
  - bumps claude-code 2.1.104 → 2.1.114

anthropics/claude-code#49512 (parallel-mkdir ENOENT race) was filed
against 2.1.112 and is still OPEN but has zero comments. Inspection
of the 2.1.114 install shows the CLI moved to a per-platform native
binary (openabdev#2.1.113) and session state relocated from
`/tmp/claude-<uid>/…/tasks/` to `$HOME/.claude/{projects,tasks}/`,
so the vulnerable filesystem layout no longer exists — binary-grep
for `/home-//tasks` returns 0 matches. 50 parallel `claude -p` calls
as the same user (40ms stagger) produced 0/50 errors, 0 bytes stderr,
and no `/tmp/claude-*` directories.

Refs openabdev#326, openabdev#412, openabdev#418, openabdev#447
Closes nothing explicitly (no issue filed; repro + rationale in body).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@brettchien brettchien requested a review from thepagent as a code owner April 19, 2026 15:22
@github-actions github-actions bot added closing-soon PR missing Discord Discussion URL — will auto-close in 3 days and removed closing-soon PR missing Discord Discussion URL — will auto-close in 3 days labels Apr 19, 2026
@thepagent thepagent merged commit 9266a1f into openabdev:main Apr 19, 2026
10 checks passed
@brettchien brettchien deleted the fix/claude-opus-4.7 branch April 19, 2026 23:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants